assert(TrackCooldowns, "TrackCooldowns not found!")

TC = {
	VersionBuild = "2",
	VersionMinor = "6",
	VersionMajor = "4",
}

local AceConfig = LibStub("AceConfigDialog-3.0")
local Media = LibStub("LibSharedMedia-3.0")
local L = LibStub("AceLocale-3.0"):GetLocale("TrackCooldowns")

local TrackCooldowns = TrackCooldowns
local mod = TrackCooldowns:NewModule("Display", nil, "AceComm-3.0", "AceEvent-3.0", "AceConsole-3.0", "TCLibBars-1.0")

local pairs = _G.pairs
local UnitName, UnitClass = _G.UnitName, _G.UnitClass
local tonumber, tostring, type = _G.tonumber, _G.tostring, _G.type

local barGroup = nil
local inBossFight

local function sortFunc(a, b)
	if a.isTimer ~= b.isTimer then
		return a.isTimer
	end

	if a.ownerGroup.groupByClass then
		if a.class == b.class then
			if a.value == b.value then
				return a.name > b.name
			else
				return a.value > b.value
			end
		else
			return a.class > b.class
		end
	else
		if a.value == b.value then
			return a.name > b.name
		else
			return a.value > b.value
		end
	end
end

local playerName

local defaults = {
	profile = {
		enabled				= true,

		locked				= false,
		scale				= 100.0,
		texture				= "Aluminium",
		growUp				= true,
		clamped				= true,
		fontFace			= nil,
		fontSize			= 12,
		orientation			= 3,
		alpha				= 100.0,
		
		width				= 180,
		height				= 16,
		
		showOwn				= false,
		hideSelf			= false,
		spells				= { },
		
		inRaids				= true,
		inParties			= true,
		inBattlegrounds		= true,
		
		readyMessage		= true,
		startMessage		= true,
		reportChannel		= "SELF",
		useCustomChannel	= false,
		customChannel		= "",

		casterName			= true,
		spellName			= true,
		tooltip				= true,
	}
}

local optFrame

function mod:OnInitialize()
end

function mod:OnEnable()
	self.db = LibStub("AceDB-3.0"):New("TC_DB", nil, "Default")
	
	if not self.db.profile.spells or #self.db.profile.spells == 0 then
		for k,v in pairs(TrackCooldowns.cooldowns) do
			for k2, v2 in pairs(v) do
				defaults.profile.spells[v2.id] = true
			end
		end
	end

	self.db:RegisterDefaults(defaults)
	optFrame = AceConfig:AddToBlizOptions(L["Track Cooldowns"], L["Track Cooldowns"])
	playerName = UnitName("player")
	
	local function DbmTCCallbackDisplay(event, dbmModule)
		--TrackCooldowns:Print("Display: Kill/Wipe detected by DBM, reset")
		mod:ResetCDsAfterWipe()
	end
	
	if DBM then
		TrackCooldowns:Print(L["Using %s for boss kill tracking"]:format("DBM"))
		DBM.RegisterCallback(mod, "kill", DbmTCCallbackDisplay)
		DBM.RegisterCallback(mod, "wipe", DbmTCCallbackDisplay)
	elseif BigWigsLoader then
		TrackCooldowns:Print(L["Using %s for boss kill tracking"]:format("BigWigs"))
		BigWigsLoader.RegisterMessage(mod, "BigWigs_OnBossEngage", function (event, bwMod)
			--TrackCooldowns:Print("Display: Pull detected by BigWigs")
			inBossFight = true 
		end)
		BigWigsLoader.RegisterMessage(mod, "BigWigs_OnBossWin", function(event, bwMod) 
			--TrackCooldowns:Print("Display: Kill detected by BigWigs")
			mod:ResetCDsAfterWipe()
			inBossFight = nil
		end)
		BigWigsLoader.RegisterMessage(mod, "BigWigs_OnBossWipe", function(event, bwMod)
			--TrackCooldowns:Print("Display: Wipe detected by BigWigs")
			if inBossFight then
				mod:ResetCDsAfterWipe()
				inBossFight = nil
			end
		end)
	--[[elseif DXE then
		TrackCooldowns:Print(L["Using %s for boss kill tracking"]:format("DXE"))
		DXE.RegisterCallback(TrackCooldowns, "TriggerDefeat", dxeTCCallback)]]
	end
	
	self:CreateFrame()
	self:SetAnchors(true)
	self:UpdateDisplay()
end

function mod:OnDisable()
end

function mod:StartCooldown(sender, spellId, cooldown, event, destGUID, playerGUID, duration, altDuration)
	if self.db.profile.showOwn and sender ~= playerName then return end
	if self.db.profile.hideSelf and sender == playerName then return end
	if not self.db.profile.spells[spellId] then return end
	if not self.db.profile.enabled then	return end
	
	if barGroup and self.db.profile.groupByClass then
		barGroup.groupByClass = true
	elseif barGroup and not self.db.profile.groupByClass then
		barGroup.groupByClass = nil
	end
	
	local firstBar = barGroup:GetBar(sender .. "_" .. spellId .. "_1")
	local secondBar = barGroup:GetBar(sender .. "_" .. spellId .. "_2")
	local spellTitle

	if self.db.profile.casterName and self.db.profile.spellName then
		spellTitle = sender .. ": " .. GetSpellInfo(spellId)
	elseif self.db.profile.casterName then
		spellTitle = sender
	elseif self.db.profile.spellName then
		spellTitle = GetSpellInfo(spellId)
	else
		spellTitle = ""
	end
	
	if firstBar == nil or secondBar == nil then
		local multiSpell = ""
		if firstBar == nil then
			multiSpell = "_1"
		else
			multiSpell = "_2"
		end

		if (firstBar ~= nil or secondBar ~= nil) and (spellId ~= 1022 and spellId ~= 1038 and spellId ~= 1044 and spellId ~= 6940) then
			return
		end

		local _, _, classIndex = UnitClass(sender)
		if classIndex == nil then
			classIndex = 1
		end
		
		if not self.db.profile.groupByClass then
			classIndex = 0
		end

		bar = barGroup:NewTimerBar((sender .. "_" .. spellId .. multiSpell), spellTitle, cooldown, cooldown, spellId, nil, classIndex)
		bar.caster  = sender
		bar.spellId = spellId
		bar.spellName = GetSpellInfo(spellId)
		
		for k, v in pairs(TrackCooldowns.totems) do
			if v == spellId then
				bar.destGUID = destGUID
				bar.playerGUID = playerGUID
				bar.duration = duration
				bar.altDuration = altDuration
			end
		end
		
		-- If a copy of the spell is already on cooldown, don't start this cooldown. This will occur when:
		-- The second bar isn't nil: second bar is only ever created if there was another bar before it
		-- This is a second copy of the spell
		local previousBar = nil
		if firstBar ~= nil then
			previousBar = firstBar
		elseif secondBar ~= nil then
			previousBar = secondBar
		end
		
		if previousBar ~= nil then
			local maxCd = cooldown * 2
			local difference = cooldown - previousBar.value
			bar:SetTimer(maxCd - difference, maxCd)
		end
		
		if self.db.profile.tooltip then
			bar:SetScript("OnEnter", function(bar, ...) 
				GameTooltip:SetOwner(bar, "ANCHOR_TOPRIGHT")
				GameTooltip:SetText(bar.caster .. ": " .. bar.spellName)
				GameTooltip:Show()
			end)
			bar:SetScript("OnLeave", function(frame, ...)
				GameTooltip:Hide()
			end)
			bar:EnableMouse(true)
		else
			bar:EnableMouse(false)
		end

		if self.db.profile.announceClick then
			local spellLink
			spellLink = GetSpellLink(spellId)
			bar:SetScript("OnMouseDown", function(bar, button)
				if spellLink and button == "LeftButton" then
					if self.db.profile.useCustomChannel and self.db.profile.customChannel ~= "" then
						SendChatMessage(L["Track Cooldowns"] .. ": " ..L["%s's %s has %s left on its cooldown."]:format(sender, spellLink, date("%M:%S", bar.value)), "CHANNEL", nil, self.db.profile.customChannel)
					elseif self.db.profile.reportChannel == "SELF" then
						TrackCooldowns:Print(L["%s's %s has %s left on its cooldown."]:format("|cff66ccff" ..  sender .. "|r", spellLink, date("%M:%S", bar.value)))
					else
						SendChatMessage(L["Track Cooldowns"] .. ": ".. L["%s's %s has %s left on its cooldown."]:format(sender, spellLink, date("%M:%S", bar.value)), self.db.profile.reportChannel)
					end
				end
			end)
		end

		if self.db.profile.startMessage then
			local spellLink
			spellLink = GetSpellLink(spellId)
			if spellLink then
				if self.db.profile.useCustomChannel and self.db.profile.customChannel ~= "" then
					SendChatMessage(L["Track Cooldowns"] .. ": " .. L["%s has casted %s."]:format(sender, spellLink), "CHANNEL", nil, self.db.profile.customChannel)
				elseif self.db.profile.reportChannel == "SELF" then
					TrackCooldowns:Print(L["%s has casted %s."]:format("|cff66ccff" ..  sender .. "|r", spellLink))
				else
					SendChatMessage(L["Track Cooldowns"] .. ": " .. L["%s has casted %s."]:format(sender, spellLink), self.db.profile.reportChannel)
				end
			end
		end

	else
		bar:SetTimer(cooldown, bar.maxValue)
	end
	
	local _, c = UnitClass(sender)
	if not c then
		for k, v in pairs(TrackCooldowns.cooldowns) do
			for k2, v2 in pairs(v) do
				if v2.id == spellId then
					c = k
				end
			end
		end
	end
	
	local color = RAID_CLASS_COLORS[c]

	if type(color) == "table" then
		bar:SetColorAt(1.00, color.r, color.g, color.b, 1)
		bar:SetColorAt(0.00, color.r, color.g, color.b, 1)
	end

end

function mod:StopCooldown(sender, spellId)
	local bar = barGroup:GetBar(sender .. "_" .. spellId .. "_1")

	if bar ~= nil then
		barGroup:RemoveBar(bar)
		self:UpdateDisplay()
	end

end

function mod:ResetCooldown(sender, spellId)
	for k, v in pairs(TrackCooldowns.resetCooldowns) do
		if k == spellId then
			for k2, v2 in pairs(v) do
				local resetBar = barGroup:GetBar(sender .. "_" .. v2 .. "_1")
				local resetBar2 = barGroup:GetBar(sender .. "_" .. v2 .. "_2")
				
				if resetBar ~= nil then
					resetBar:SetTimer(0, resetBar.maxValue)
				end
				if resetBar2 ~= nil then
					resetBar2:SetTimer(0, resetBar2.maxValue)
				end
			end
		end
	end
end

function mod:HandleTotems(destGUID)
	local bars = barGroup:GetBars()
	
	if type(bars) == "table" then
		for k, v in pairs(bars) do
			if v.destGUID and v.destGUID == destGUID then
				local playerInfo = TrackCooldowns.LGIST:GetCachedInfo(v.playerGUID)
				local hasTotemicRestoration = playerInfo and next(playerInfo.talents) and playerInfo.talents[108284]
				if not hasTotemicRestoration then
					return
				end
				
				local hasGlyphFireElemental = playerInfo and next(playerInfo.glyphs) and playerInfo.glyphs[55455]
				local duration = v.duration
				if v.altDuration and hasGlyphFireElemental and v.spellId == 2894 then
					duration = v.altDuration
				end	

				if (v.maxValue - v.value) < duration and not v.isReduced then	
					local durationUp = v.maxValue - v.value
					local durationLeft = duration - durationUp
					local cooldownReductionPercentage = (durationLeft / duration) * 0.5
					local cooldownReduction = v.maxValue * cooldownReductionPercentage - 0.5

					if cooldownReduction > (v.maxValue / 2) then
						cooldownReduction = v.maxValue / 2
					end
					v.isReduced = true
					v:SetTimer(v.value - cooldownReduction, v.maxValue)
				end
				return
			end
		end
	end
end

function mod:HandleUnbreakableSpirit(srcName)
	local bars = barGroup:GetBars()
	
	if type(bars) == "table" then
		for k, v in pairs(bars) do
			local spellId = v.spellId
			if spellId == 498 or spellId == 642 or spellId == 633 then
				if not v.reducedAmount then
					v.reducedAmount = 0
				end
				if v.reducedAmount and v.reducedAmount < (v.maxValue / 2) then
					local reducedAmount = v.maxValue * 0.03					
					v.reducedAmount = v.reducedAmount + reducedAmount
					v:SetTimer(v.value - reducedAmount, v.maxValue)
				end
			end
		end
	end
end

function mod:PLAYER_LOGIN()
end

--[[		Configuration Methods		]]--
function mod:TrackCooldownsToggle()
	self = mod

	if self.db.profile.enabled then
		self.db.profile.enabled = false
		AceConfig:Close(L["Track Cooldowns"], configFrame)
		TrackCooldowns:Print(L["is disabled."])
	else
		self.db.profile.enabled = true
		AceConfig:Close(L["Track Cooldowns"], configFrame)
		TrackCooldowns:Print(L["is enabled."])
	end

end

function mod:ShowVersion()
	TrackCooldowns:Print(L["Version"] .. " - |cff66ccff" .. TC.VersionMajor .. "." .. TC.VersionMinor .. "." .. TC.VersionBuild .. "|r")
end

function mod:ShowConfig()
	AceConfig:SetDefaultSize(L["Track Cooldowns"], 600, 550)
	AceConfig:Open(L["Track Cooldowns"], configFrame)
end

function mod:Test()
	self = mod
	local i = 1
	for _, v in pairs(TrackCooldowns.cooldowns) do
		for _, v2 in pairs(v) do
			self:StartCooldown(L["Test"], v2.id, i * 2, "TEST")
			i = i + 1
		end
	end
end

function mod:ResetCDsAfterWipe()
	if not self.db.profile.enabled then return end
	
	local removed = false
	local bars = barGroup:GetBars()
	
	if type(bars) == "table" then
		for k, v in pairs(bars) do
			if v.maxValue >= 300 and v.spellId ~= 1022 and v.spellId ~= 20608 then
				barGroup:RemoveBar(k)
				removed = true
			end
		end
	end
	
	if removed then
		self:UpdateDisplay()
	end
end

function mod:Reset()
	local bars = barGroup:GetBars()

	if type(bars) == "table" then
		for k, v in pairs(bars) do
			barGroup:RemoveBar(k)
		end
	end

end

function mod:SpellToggle(spell, val)
	if val == true then return end
	local bars = barGroup:GetBars()
	local removed = false

	if type(bars) == "table" then
		for k, v in pairs(bars) do
			if spell == v.spellId then
				barGroup:RemoveBar(k)
				removed = true
			end
		end
	end

	if removed then
		self:UpdateDisplay()
	end
end

--[[
function dxeTCCallback(event, encounter)
	--TrackCooldowns:Print("Wipe detected by DXE")
	mod:ResetCDsAfterWipe()
end]]

--[[		Bar Group Anchor		]]--

function mod:CreateFrame()
	barGroup = nil
	barGroup = self:NewBarGroup(L["Track Cooldowns"], nil, self.db.profile.width, self.db.profile.height, "TCD_Anchor")
	barGroup:SetFlashPeriod(0)
	barGroup:SetSortFunction(sortFunc)
	barGroup.RegisterCallback(self, "AnchorClicked")
	barGroup.RegisterCallback(self, "AnchorMoved")
	barGroup.RegisterCallback(self, "TimerFinished")
end

function mod:UpdateDisplay()
	if not barGroup then return end
	
	if self.db.profile.locked then
		barGroup:Lock()
		barGroup:HideAnchor()
	else
		barGroup:Unlock()
		barGroup:ShowAnchor()
	end

	barGroup:SetOrientation(self.db.profile.orientation)
	barGroup:SetClampedToScreen(self.db.profile.clamped)
	barGroup:SetFont(Media:Fetch("font", self.db.profile.fontFace), self.db.profile.fontSize)
	barGroup:SetTexture(Media:Fetch("statusbar", self.db.profile.texture))
	barGroup:SetScale(self.db.profile.scale / 100.0)
	barGroup:ReverseGrowth(self.db.profile.growUp)
	barGroup:SetAlpha(self.db.profile.alpha / 100.0)
	barGroup:SetWidth(self.db.profile.width)
	barGroup:SetHeight(self.db.profile.height)
end

function mod:SetAnchors(useDB)
	local t = self.db.profile.growUp
	local x, y

	if useDB then
		x, y = self.db.profile.posX, self.db.profile.posY
		if not x and not y then
			barGroup:ClearAllPoints()
			barGroup:SetPoint("CENTER", UIParent, "CENTER", 0, 0)
			return
		end
	elseif t then
		x, y = barGroup:GetLeft(), barGroup:GetBottom()
	else
		x, y = barGroup:GetLeft(), barGroup:GetTop()
	end

	barGroup:ClearAllPoints()

	if t then
		barGroup:SetPoint("BOTTOMLEFT", UIParent, "BOTTOMLEFT", x, y)
	else
		barGroup:SetPoint("TOPLEFT", UIParent, "BOTTOMLEFT", x, y)
	end

	self.db.profile.posX, self.db.profile.posY = x, y
end

function mod:AnchorClicked(cbk, group, button)
	if button == "RightButton" then
		self:ShowConfig()
	end
end

function mod:ToggleReport()
	local bars = barGroup:GetBars()
	
	if showReport == false then
		showReport = true
	else
		showReport = false
	end
	
	if type(bars) == "table" then
		for k, v in pairs(bars) do
			if showReport == true then
				if not v.isReport then
					v:Hide()
				else
					v.Show()
				end
			else 
				if not v.isReport then
					v:Show()
				else
					v:Hide()
				end
			end
		end
	end
end

function mod:AnchorMoved(cbk, group, x, y)
	self:SetAnchors()
end

function mod:TimerFinished(cbk, group, bar, name)
	if not self.db.profile.readyMessage then return end
	local spellLink
	spellLink = GetSpellLink(bar.spellId)
	local firstBar = barGroup:GetBar(bar.caster .. "_" .. bar.spellId .. "_1")
	local secondBar = barGroup:GetBar(bar.caster .. "_" .. bar.spellId .. "_2")

	if firstBar then
		if firstBar.isReduced then
			firstBar.isReduced = nil
		end
		if firstBar.reducedAmount then
			firstBar.reducedAmount = nil
		end
	end
	if secondBar then
		if secondBar.isReduced then
			secondBar.isReduced = nil
		end
		if secondBar.reducedAmount then
			secondBar.reducedAmount = nil
		end
	end
	
	if spellLink then
		if self.db.profile.useCustomChannel and self.db.profile.customChannel ~= "" then
			if self.db.profile.onlyReportSelf then
				if playerName == bar.caster then
					SendChatMessage(L["Track Cooldowns"] .. ": " .. L["%s's %s is up."]:format(bar.caster, spellLink), "CHANNEL", nil, self.db.profile.customChannel)
				end
			else
				SendChatMessage(L["Track Cooldowns"] .. ": " .. L["%s's %s is up."]:format(bar.caster, spellLink), "CHANNEL", nil, self.db.profile.customChannel)
			end
		elseif self.db.profile.reportChannel == "SELF" then
			TrackCooldowns:Print(L["%s's %s is up."]:format("|cff66ccff" ..  bar.caster .. "|r", spellLink))
		else
			if self.db.profile.onlyReportSelf then
				if playerName == bar.caster then
					SendChatMessage(L["Track Cooldowns"] .. ": " .. L["%s's %s is up."]:format(bar.caster, spellLink), self.db.profile.reportChannel)
				end
			else
				SendChatMessage(L["Track Cooldowns"] .. ": " .. L["%s's %s is up."]:format(bar.caster, spellLink), self.db.profile.reportChannel)
			end
		end
	end
end

function mod:SetBarScripts()
	local bars = barGroup:GetBars()
	if type(bars) == "table" then
		for k, v in pairs(bars) do
			local spellLink
			spellLink = GetSpellLink(v.spellId)
			
			if self.db.profile.announceClick then
				v:SetScript("OnMouseDown", function(v, button)
					if spellLink and button == "LeftButton" then
						if self.db.profile.useCustomChannel and self.db.profile.customChannel ~= "" then
							SendChatMessage(L["Track Cooldowns"] .. ": " ..L["%s's %s has %s left on its cooldown."]:format(v.caster, spellLink, date("%M:%S", v.value)), "CHANNEL", nil, self.db.profile.customChannel)
						elseif self.db.profile.reportChannel == "SELF" then
							TrackCooldowns:Print(L["%s's %s has %s left on its cooldown."]:format("|cff66ccff" ..  v.caster .. "|r", spellLink, date("%M:%S", v.value)))
						else
							SendChatMessage(L["Track Cooldowns"] .. ": " ..L["%s's %s has %s left on its cooldown."]:format(v.caster, spellLink, date("%M:%S", v.value)), self.db.profile.reportChannel)
						end
					end
				end)
			end
		end
	end
end

function mod:RemoveBarScripts()
	local bars = barGroup:GetBars()
	if type(bars) == "table" then
		for k, v in pairs(bars) do
			v:SetScript("OnMouseDown", nil)
		end
	end
end

function mod:ResortBars(groupByClass)
	if barGroup then
		barGroup.groupByClass = groupByClass
		barGroup:SortBars()
	end
end